package com.edee.bigday.ware.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.edee.bigday.ware.dao.WareSkuDao;
import com.edee.bigday.ware.entity.WareOrderTaskDetailEntity;
import com.edee.bigday.ware.entity.WareOrderTaskEntity;
import com.edee.bigday.ware.entity.WareSkuEntity;
import com.edee.bigday.ware.feign.CommodityFeignService;
import com.edee.bigday.ware.feign.OrderFeignService;
import com.edee.bigday.ware.service.WareOrderTaskDetailService;
import com.edee.bigday.ware.service.WareOrderTaskService;
import com.edee.bigday.ware.service.WareSkuService;
import com.edee.bigday.ware.vo.OrderItemVo;
import com.edee.bigday.ware.vo.OrderVo;
import com.edee.bigday.ware.vo.SkuHasStockVo;
import com.edee.bigday.ware.vo.WareSkuLockVo;
import com.edee.common.exception.NoStockException;
import com.edee.common.to.mq.OrderTo;
import com.edee.common.to.mq.StockDetailTo;
import com.edee.common.to.mq.StockLockedTo;
import com.edee.common.utils.PageUtils;
import com.edee.common.utils.Query;
import com.edee.common.utils.R;
import lombok.Data;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


@Service("wareSkuService")
public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {

    @Autowired
    WareSkuDao wareSkuDao;

    @Autowired
    CommodityFeignService commodityFeignService;

    @Autowired
    WareOrderTaskService wareOrderTaskService;

    @Autowired
    WareOrderTaskDetailService wareOrderTaskDetailService;

    @Autowired
    OrderFeignService orderFeignService;

    @Autowired
    RabbitTemplate rabbitTemplate;

    /**
     * 库存自动解锁：
     * 下单成功，库存锁定成功，但是后续的其它业务调用出现异常，导致订单回滚，此时也需要解锁库存。
     * TODO 存在的问题：解锁库存的消息被消费后，无论解锁成功与否，消息均被删除。应该修正为：解锁库存失败时，一定要告诉服务。
     * 解决办法：启动消息队列的手动ACK机制。
     * spring.rabbitmq.listener.simple.acknowledge-mode=manual
     */

    private void unlockStock(Long skuId, Long wareId, Integer skuNum, Long detailId) {
        //库存解锁：
        wareSkuDao.unlockStock(skuId, wareId, skuNum);
        //更新库存工作单详情表信息：
        WareOrderTaskDetailEntity detailEntity = new WareOrderTaskDetailEntity();
        //设置Id，作为更新条件：
        detailEntity.setId(detailId);
        //设置需要修改的字段的值：
        detailEntity.setLockStatus(2);
        wareOrderTaskDetailService.updateById(detailEntity);
    }

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        QueryWrapper<WareSkuEntity> queryWrapper = new QueryWrapper<>();

        String skuId = (String) params.get("skuId");
        if (!StringUtils.isEmpty(skuId)) {
            queryWrapper.eq("sku_id", skuId);
        }

        String wareId = (String) params.get("wareId");
        if (!StringUtils.isEmpty(wareId)) {
            queryWrapper.eq("ware_id", wareId);
        }

        IPage<WareSkuEntity> page = this.page(
                new Query<WareSkuEntity>().getPage(params),
                queryWrapper
        );

        return new PageUtils(page);
    }

    @Override
    public void addStock(Long skuId, Long wareId, Integer skuNum) {
        //1、如果要增加的库存记录不存在，则新建：
        List<WareSkuEntity> wareSkuEntities = wareSkuDao.selectList(new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId).eq("ware_id", wareId));
        if (wareSkuEntities == null || wareSkuEntities.size() == 0) {
            WareSkuEntity wareSkuEntity = new WareSkuEntity();
            wareSkuEntity.setSkuId(skuId);
            wareSkuEntity.setWareId(wareId);
            wareSkuEntity.setStock(skuNum);
            wareSkuEntity.setStockLocked(0);
            //远程查询sku的名称：
            try {
                R info = commodityFeignService.info(skuId);
                Map<String, Object> skuInfo = (Map<String, Object>) info.get("skuInfo");
                if (info.getCode() == 0) {
                    wareSkuEntity.setSkuName((String) skuInfo.get("skuName"));
                }
            } catch (Exception e) {
            }
            wareSkuDao.insert(wareSkuEntity);
        }
        //2、如果库存记录已存在，则增加库存数量：
        else {
            wareSkuDao.addStock(skuId, wareId, skuNum);
        }
    }

    @Override
    public List<SkuHasStockVo> getSkuHasStock(List<Long> skuIds) {
        //因为要逐一检查，所以需要进行遍历：
        List<SkuHasStockVo> collect = skuIds.stream().map(skuId -> {
            SkuHasStockVo vo = new SkuHasStockVo();
            //查询当前sku的总库存量：
            //SELECT SUM(stock-stock_locked) FROM `wms_ware_sku` WHERE sku_id=10
            Long count = baseMapper.getSkuStock(skuId);
            vo.setSkuId(skuId);
            vo.setHasStock(count == null ? false : count > 0);
            return vo;
        }).collect(Collectors.toList());
        return collect;
    }

    /**
     * 为某个订单锁定库存
     * rollbackFor = NoStockException.class
     * 默认只要是运行时异常，都会回滚，所以(rollbackFor = NoStockException.class)可以不加。
     *
     * @param vo
     * @return 库存解锁的场景：
     * 1）下单成功后，超过支付时限未支付，被系统自动取消，或者用户手动取消，均需要解锁库存。
     * 2）下单成功，库存锁定成功，但是后续的其它业务调用出现异常，导致订单回滚，此时也需要解锁库存。
     */
    @Transactional
    @Override
    public boolean orderLockStock(WareSkuLockVo vo) {

        /**
         * 保存库存工作单信息，以便后续出现异常时追溯。
         */
        WareOrderTaskEntity wareOrderTaskEntity = new WareOrderTaskEntity();
        wareOrderTaskEntity.setOrderSn(vo.getOrderSn());
        wareOrderTaskService.save(wareOrderTaskEntity);

        //1、在实际业务场景中：按照下单的收货地址，就近选择仓库，锁定库存：

        //2、(暂不考虑上述就近选择仓库场景) 找到每个商品有库存的仓库：
        List<OrderItemVo> locks = vo.getLocks();
        List<SkuWareHasStock> wares = locks.stream().map(item -> {
            SkuWareHasStock wareSkuHasStock = new SkuWareHasStock();
            Long skuId = item.getSkuId();
            wareSkuHasStock.setSkuId(skuId);
            wareSkuHasStock.setNum(item.getCount());
            //查询这个商品有库存的仓库Id：
            List<Long> wareIds = wareSkuDao.listWareIdHasSkuStock(skuId);
            wareSkuHasStock.setWareIds(wareIds);
            return wareSkuHasStock;
        }).collect(Collectors.toList());


        //Boolean allLocked = true;
        //3、锁定库存：
        for (SkuWareHasStock ware : wares) {
            //3.1）如果每一个商品都锁定成功，则将包含当前工作单被锁定时的工作单id、仓库id、锁定数量等信息的工作单记录发送给MQ；
            //3.2）如果锁定失败，则上面保存的工作单信息就会回滚。如果只发送id，工作单信息回滚后，工作单id就无法查到，
            // 就无法查到锁定的商品信息。所以，不能只发工作单id，而应该发送工作单详情完整信息。
            Boolean currSkuLocked = false;
            Long skuId = ware.getSkuId();
            List<Long> wareIds = ware.getWareIds();
            Integer num = ware.getNum();
            if (wareIds == null || wareIds.size() == 0) {
                throw new NoStockException(skuId);
            }
            for (Long wareId : wareIds) {
                Long count = wareSkuDao.lockSkuStock(skuId, wareId, num);
                if (count == 1) {
                    currSkuLocked = true;
                    //库存锁定成功，向MQ发送一条消息：
                    WareOrderTaskDetailEntity detailEntity = new WareOrderTaskDetailEntity(null, skuId, "", num, wareOrderTaskEntity.getId(), wareId, 1);
                    wareOrderTaskDetailService.save(detailEntity);
                    StockLockedTo stockLockedTo = new StockLockedTo();
                    stockLockedTo.setId(wareOrderTaskEntity.getId());
                    //stockLockedTo.setDetailId(detailEntity.getId());
                    //修改为保证工作单详情完整信息：
                    StockDetailTo stockDetailTo = new StockDetailTo();
                    BeanUtils.copyProperties(detailEntity, stockDetailTo);
                    stockLockedTo.setDetailTo(stockDetailTo);
                    rabbitTemplate.convertAndSend("stock-event-exchange", "stock.locked", stockLockedTo);
                    break;
                } else {
                    //库存锁定失败，尝试下一个仓库：
                }
            }
            if (currSkuLocked == false) {
                //当前商品在所有库存中均未锁住：
                throw new NoStockException(skuId);
            }
        }
        //4、所有商品库存全部锁定成功：
        return true;
    }

    @Override
    public void unlockStock(StockLockedTo to) {
        StockDetailTo detailTo = to.getDetailTo();
        //Long skuId = detailTo.getSkuId();
        Long detailId = detailTo.getId();

        //解锁库存逻辑：
        //1、在数据库中查看库存锁定信息：
        // 1）如果能查到，则说明库存锁定成功。是否需要解锁，还需要查询订单的情况：
        //  1.1）订单不存在，需要解锁：
        //  1.2）订单存在，且状态为“已取消”，则需要解锁。
        //  1.3）若订单存在，且状态正常，则不需要解锁库存。
        WareOrderTaskDetailEntity byId = wareOrderTaskDetailService.getById(detailId);
        if (byId != null) {
            //解锁之前：
            //查询库存工作单的id：
            Long id = to.getId();
            //根据库存工作站的id查询工作单的全量信息：
            WareOrderTaskEntity orderTaskEntity = wareOrderTaskService.getById(id);
            //从库存工作单全量信息中获取订单号orderSn：
            String orderSn = orderTaskEntity.getOrderSn();
            //根据订单号查询订单的状态（需要远程调用订单服务）：
            R r = orderFeignService.getOrderEntity(orderSn);
            if (r.getCode() == 0) {
                //订单数据返回成功：
                OrderVo data = r.getData(new TypeReference<>() {
                });
                //Integer status = data.getStatus(); //当data为null时，此句出现异常，所以，应该将data.getStatus()直接用在if中。
                if (data == null || data.getStatus() == 4) {
                    //之前写成了下面的!=4，造成了死循环：
                    //if (data == null || status != 4) {
                    //订单状态为已取消(已关闭)，需要解锁库存：
                    if (byId.getLockStatus() == 1) {
                        //只有当库存工作单详情表里的lock_statusz字段为1(已锁定)时，才允许解锁：
                        unlockStock(detailTo.getSkuId(), detailTo.getWareId(), detailTo.getSkuNum(), detailId);
                    }
                    //channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
                } else {
                    //拒绝消息，并重新放回队列，让消费者继续消费，继续尝试解锁：
                    //channel.basicReject(message.getMessageProperties().getDeliveryTag(), true);
                    throw new RuntimeException("远程服务调用失败！");
                }
            }
        } else {
            // 2）如果未查到，则说明锁定失败，无须解锁。
            //channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
        }
    }

    //订单解锁后，发送消息到MQ，使得库存也能进行解锁，防止如下情况发送：
    // 库存解锁的消息先到期，就会去查订单的状态，而此时订单由于某种原因仍处于新建状态，
    // 库存就不进行解锁。如果后续订单被取消，则锁定的库存将永远得不到释放。
    // 此种情况应该是主要的库存解锁逻辑，库存服务自身检查库存锁定信息、订单状态进行解锁的逻辑应该作为补偿逻辑。
    @Transactional
    @Override
    public void unlockStock(OrderTo orderTo) {
        //先获取到订单号：
        String orderSn = orderTo.getOrderSn();
        //根据订单号查到库存工作单的信息：
        WareOrderTaskEntity wareOrderTaskEntity = wareOrderTaskService.getWareOrderTaskByOrderSn(orderSn);
        //获取库存工作单的Id：
        Long taskEntityId = wareOrderTaskEntity.getId();
        //根据库存工作单Id，找到所有未解锁(lock_status为1)的库存记录，进行解锁：
        List<WareOrderTaskDetailEntity> taskDetailEntityList = wareOrderTaskDetailService.list(new QueryWrapper<WareOrderTaskDetailEntity>()
                .eq("task_id", taskEntityId).eq("lock_status", 1));
        //将上述库存记录逐一解锁：
        //Long skuId, Long wareId, Integer skuNum, Long detailId
        for (WareOrderTaskDetailEntity entity : taskDetailEntityList) {
            unlockStock(entity.getSkuId(),entity.getWareId(),entity.getSkuNum(),entity.getId());
        }
    }

    //仓库中有库存信息内部类：
    @Data
    class SkuWareHasStock {
        private Long skuId;
        private List<Long> wareIds;
        private Integer num;
    }

}